# -*- coding: utf-8 -*-

import os
import sys
import time
import signal
import subprocess
from threading import Thread
from log.logger import logger


def _kill_all_processes(pid):
    ''' kill pid and its child processes '''

    if pid <= 1:
        return

    #logger.debug("Killing all processes of [{0}].".format(pid))
    try:
        # find the all child pids
        cmd = "ps -o pid --ppid {0} --noheaders".format(pid)
        # not use shell, use list to describe cmd
        p = subprocess.Popen(cmd.split(" "), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        (out, err) = p.communicate()
        ret = p.returncode
        out = out.strip()
        err = err.strip()

        # recursive kill processes
        if ret == 0 and out != None and len(out) > 0:
            for pid_str in out.split("\n"):
                _kill_all_processes(int(pid_str))
    except Exception, e:
        p.kill()
        p.wait(5)
        logger.warn("Get child processes of [{0}] failed: [{1}].".format(pid, e))
        pass

    try:
        os.kill(pid, signal.SIGKILL)
    except:
        pass
    #logger.debug("Killed process [{0}].".format(pid))


class ExecThread(Thread):
    def __init__(self, cmd, log_file):
        super(ExecThread, self).__init__()
        self.cmd = cmd
        self.log_file = log_file
        self.process = None
        self.result = None
        self.exec_time = None
        self.error = None

    def run(self):
        try:
            # start time
            start_time = time.time()

            # execute command
            if self.log_file is None:
                p = subprocess.Popen(self.cmd, shell=True, executable="/bin/bash", stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                self.process = p

                # get the result
                out, err = p.communicate()
                ret = p.returncode
                out = out.strip()
                err = err.strip()
                result = {'ret': ret, 'out': out, 'err': err}
                self.result = result
            else:
                # python script, add parameter -u to remove the print buffer
                if self.cmd.startswith('python'):
                    cmd_list = self.cmd.split(" ")
                    cmd_list.insert(1, '-u')
                    self.cmd = " ".join(cmd_list)

                # err is redirected to out
                p = subprocess.Popen(self.cmd, shell=True, executable="/bin/bash", stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
                self.process = p

                # write the out/err to log_file
                while True:
                    line = p.stdout.readline()
                    with open(self.log_file, 'a') as f:
                        f.write(line)
                    if line == '' and p.poll() is not None:
                        break
                logger.info("The out and err of cmd [{0}] are all redirected to log file [{1}].".format(self.cmd, self.log_file))

                # get the result
                ret = p.returncode
                result = {'ret': ret, 'out': '', 'err': ''}
                self.result = result

            # end time
            end_time = time.time()

            # execute time
            exec_time = end_time - start_time
            exec_time = round(exec_time, 3)
            self.exec_time = exec_time
        except Exception, e:
            self.error = e

    def stop(self):
        self._Thread__stop()


# the default timeout is 1 day
def exec_cmd(cmd, remote_host=None, log_file=None, timeout=86400):
    if remote_host is not None:
        # $ means to get the value in shell
        cmd = cmd.replace("$", "\$")
        cmd = "ssh -o 'StrictHostKeyChecking no' -o 'UserKnownHostsFile /dev/null' -o ConnectTimeout=10 -o ConnectionAttempts=3 \
            root@{0} \"{1}\"".format(remote_host, cmd)

    if log_file is not None:
        if not os.path.exists(log_file):
            os.system("mkdir -p {0}".format(os.path.dirname(log_file)))
            os.system("> {0}".format(log_file))

    # start a new thread to execute the command
    t = ExecThread(cmd, log_file)
    t.start()
    # block the main thread, until the sub thread (t) is finished or timeout
    t.join(timeout)

    if t.error is not None:
        err = 'Execute the command error!'
        result = {'ret': 1, 'out': '', 'err':err}
        logger.error('Execute the command [{0}] Error [{1}]!'.format(cmd, t.error))
    elif t.result is None:
        err = 'Execute the command timeout!'
        result = {'ret': 1, 'out': '', 'err':err}
        logger.error('Execute the command [{0}] Timeout [{1}]s!'.format(cmd, timeout))
    else:
        result = t.result
        logger.info('The command [{0}] is executed OK. Result [{1}], Exec time [{2}]s.'.format(cmd, t.result, t.exec_time))

    # kill processes
    if t.process:
        _kill_all_processes(t.process.pid)

    # stop the thread
    if t.isAlive():
        t.stop()

    return result


# the default timeout is 1 day
def safe_exec_cmd(cmd, remote_host=None, log_file=None, timeout=86400):
    result = exec_cmd(cmd, remote_host, log_file, timeout)
    if result['ret'] != 0:
        logger.error('The command [{0}] is executed Failed. Error is [{1}].'.format(cmd, result['err']))
        # exit the process
        # exit thread if it is in a sub thread
        sys.exit(1)

    return result

